#include "dirty.h"
#include "log.h"
#include "config.h"
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <fstream>
#include <sstream>
#include <arpa/inet.h>

#include <mysql.h>

MYSQL g_conn;
MYSQL_RES *res_ptr;
MYSQL_ROW sqlrow;

extern CConfig g_config;
Dirty* Dirty::pDirty = NULL;

Dirty::Dirty():pShare(NULL),pNewMem(false),pMemHead(NULL)
{
	pthread_mutex_init(&mutex, NULL);
}


bool Dirty::init(key_t shmKey)
{
	int size = sizeof(DIRTY_MEM_HEAD) + 65536*(sizeof(SLOT_ENTRY)) + 30000 * sizeof(DIRTY_ENTRY);
	
	if(!getShareMem(shmKey,size))
	{
		return false;		
	}
	
	if(pNewMem)
	{
		bzero(pShare, size);
	}
	

	log_info("new mem init...");

	pMemHead->curOffset = 0;
	//pMemHead->segOffset = sizeof(DIRTY_MEM_HEAD);
	pMemHead->bottomOffset = 30000;
	
	bzero(pShare + sizeof(DIRTY_MEM_HEAD),size - sizeof(DIRTY_MEM_HEAD));


	return true;
}

bool Dirty::getShareMem(key_t key,size_t size)
{
	int iShmID;
	char* sShm;
	int flag = IPC_CREAT | IPC_EXCL | S_IRUSR| S_IWUSR ;
	memkey = key;
	
	if ((iShmID = shmget(key, size, flag)) < 0)
	{
		//check if is exist already
		if(errno == EEXIST)
		{
			log_info("shm %x already exist",(unsigned int)key);
			pNewMem = false;
			
			flag = IPC_CREAT | S_IRUSR| S_IWUSR ;
			if ((iShmID = shmget(key, size, flag)) < 0)
			{
				log_info("shmget %ld %ld:failed %s", (long int)key,(long int)size,strerror(errno));
				return false;
			}
		}	
		else
		{
			log_error("shm get failed:%s",strerror(errno));
			return false;
		}	
	}
	else
	{
		pNewMem = true;
	}
	
	log_info("shmget ok %x %ld", (unsigned int)key,(long int)size);
	
	if ((sShm = (char *)shmat(iShmID, NULL ,0)) == (char *) -1)
	{
		log_error("shm attch failed:%s",strerror(errno));
		return false;
	}
	log_info("shm attch at:%ld",(long int)sShm);
	
	
	pShare = sShm;
	pMemHead = (DIRTY_MEM_HEAD*)pShare;
	
	return true;
}

PSS Dirty::getNextWord(const unsigned char* src,unsigned int start)
{
	PSS rlt;
	
	unsigned char c = (unsigned char)src[start];
	if(c < 128)
	{
		rlt.first = c;
		rlt.second = 1;
	}
	else
	{
		rlt.first = *((short*)(src+start));
		rlt.second = 2;
	}
	
	return rlt;
}


bool Dirty::checkDirty(string &src, const char* buf,  size_t len, stringstream &outbuf)
{
	MATCH_RLT rlt;
	SLOT_ENTRY* slot;
	DIRTY_ENTRY* dirtyEntry;
	PSS nlt;
	bool flag ;
	unsigned int start = 0;	

	if(buf == NULL){
		return false;
	}

	if(src.empty() ){
		return false;
	}

	if (src == "reload")
	{
		srcSet.clear();
		init(memkey);
		outbuf << "reload success\r\n";
		return true;
	}
	
	pthread_mutex_lock(&mutex);
	if(srcSet.find(src) == srcSet.end())
	{
		//bool ret = loadDirtyFromFile(src);
		bool ret = loadDirtyFromMySQL(src);
		if(!ret)
		{
			log_debug("load dirty failed");
			pthread_mutex_unlock(&mutex);
			return false;
		}
		
		log_debug("load dirty success,num = [%s]",src.c_str());

		srcSet.insert(src);
	}
	pthread_mutex_unlock(&mutex);


	log_info("begin check word src: [%s],uri = [%s]", src.c_str(),buf);

	while(true)
	{
		if(start >= len){
			break;
		}
		
		flag = false;
		nlt = getNextWord((const unsigned char*)buf,start);
		
		slot = (SLOT_ENTRY*)getEntry(nlt.first);

		if(slot->next == 0){
			start += nlt.second; 
			continue;
		}

		dirtyEntry = (DIRTY_ENTRY*)getDirtyEntry(slot->next - 1);

		do{
			//log_debug("check [%s],dirtyEntry->len = [%d],dirtyEntry->next = [%u],len = [%d], start = [%d]", dirtyEntry->words,dirtyEntry->len,dirtyEntry->next,len,start);
			if(dirtyEntry->len > len - start)
			{
				if(dirtyEntry->next == 0){
					break;
				}else{
					dirtyEntry = (DIRTY_ENTRY*)getDirtyEntry(dirtyEntry->next-1);
					continue;
				}
			}

			if(dirtyEntry->len > 0 && strcmp((char *)dirtyEntry->src, src.c_str()) == 0 && strncmp((const char*)dirtyEntry->words,(const char*)(buf+start),dirtyEntry->len) == 0 )
			{
				//strcmp((char *)dirtyEntry->src, src.c_str()) == 0 && 
				log_debug("match word");
				if(rlt.find(dirtyEntry)!=rlt.end()){
					rlt[dirtyEntry] = rlt[dirtyEntry] + 1;
				}else{
					rlt[dirtyEntry] = 1;
				}
				
				flag = true;

				log_debug("len : %d", dirtyEntry->len);
				start += dirtyEntry->len;
				break;
			}

			if(dirtyEntry->next == 0){
				break;
			}

			dirtyEntry = (DIRTY_ENTRY*)getDirtyEntry(dirtyEntry->next-1);
		}while(true);
		
		if(!flag){
			start += nlt.second; 
		}
	}

	map <DIRTY_ENTRY*, int32_t>::iterator it;
	for(it = rlt.begin(); it!=rlt.end();it++)
	{
		DIRTY_ENTRY *p = it->first;
		//outbuf << p->type << "|" << p->words << "|" << it->second << "\r\n";
		// type|word|cout|level
		outbuf << p->type << "|" << p->words << "|" << it->second << "|" << p->level << "\r\n";
		//log_debug("type = [%u],words = [%s],second =  [%u],level = [%u]",p->type,p->words,it->second,p->level);
	}
	
	return true;
}


//bool Dirty::addDirty(string &src, const string& theWord, int32_t type)
bool Dirty::addDirty(string &src, const string& theWord, pair<int8_t,int8_t> dirty)
{
	int32_t type	= dirty.first;
	int32_t level 	= dirty.second;
	SLOT_ENTRY* slot;
	DIRTY_ENTRY *entry,*entry2;
	PSS nlt = getNextWord((const unsigned char*)theWord.c_str(),0);
	
	if(theWord.length() > sizeof(entry->words))
	{
		log_error("added word %s is too long",theWord.c_str());
		return false;
	}
	
	slot = (SLOT_ENTRY*)getEntry(nlt.first);
	
	entry = (DIRTY_ENTRY*)memAlloc();
	if(!entry)
	{
		log_error("memory exhaust");
		return false;
	}
	
	
	entry->next	= 0;
	entry->len	= theWord.length();
	memcpy(entry->words,theWord.c_str(),entry->len);
	memcpy(entry->src, src.c_str(), src.size());
	entry->type	= type;
	entry->level	= level;

	log_debug("add word:%s, type:%d, level:%d, len:%d, src:%s", entry->words, entry->type, entry->level, entry->len, entry->src);
	
	
	if(slot->next == 0)
	{
		slot->next = pMemHead->curOffset;
	}
	else
	{
		entry2 = (DIRTY_ENTRY*)getDirtyEntry(slot->next - 1);


		//Maybe the word is exist, need check!
		
		while(entry2->next != 0) 
		{
			entry2 = (DIRTY_ENTRY*)getDirtyEntry(entry2->next - 1);
		}
		entry2->next = pMemHead->curOffset;
	}
	
	return true;
}


void* Dirty::memAlloc()
{
	//DIRTY_ENTRY* pDirty;
	if(pMemHead->curOffset >= pMemHead->bottomOffset)
	{
		return NULL;
	}
	
	char* start = pShare + sizeof(DIRTY_MEM_HEAD) + 65536 * sizeof(SLOT_ENTRY);
	start += pMemHead->curOffset * sizeof(DIRTY_ENTRY);
	
	pMemHead->curOffset += 1;
	
	return start; 
}


bool Dirty::loadDirtyFromFile(string &src)
{
	char buf[MAX_LINE_LEN];

	const char *srcdir = g_config.GetStrVal("DIRTY", "srcdir");
	const char *home = getenv("FILTERD_HOME");
	if(srcdir == NULL && home == NULL )
	{
		log_error("conf dir is not exist");
		return false;
	}

	string file;

	if(srcdir != NULL && strlen(srcdir) > 1)
	{
		file = srcdir;
	}
	else
	{
		file = home;
		file += "/conf";

	}

	
	file += "/";
	file += src;

	
	std::ifstream iFile(file.c_str(),std::ios::in);

	log_debug("src file = %s", file.c_str());
	
	if(iFile.fail())
	{
		log_error("load dirty words from file failed:%s-%s",file.c_str(),strerror(errno));
		//exit(1);
		return false;
	}

	char *pos = NULL;
	map<string, int8_t, std::greater<string>  > dirtymap;
	while(iFile.getline(buf,MAX_LINE_LEN-1))
	{
		//log_debug("%s", buf);
		pos = strchr(buf, '|');
		if(pos == NULL)
			return false;
		*pos = 0;
		string word = buf;
		int32_t type = atoi(pos + 1);
		dirtymap[word] = type;
		
	}

	
	map<string, int8_t>::iterator it;
	for(it=dirtymap.begin(); it!=dirtymap.end();it++)
	{
		//addDirty(src, it->first, it->second);
	}
	
	return true;
}


bool Dirty::loadDirtyFromMySQL(string &src)
{
	char sql[1024]={};

	const char* mserver	= g_config.GetStrVal("MYSQL","server");
	const char* mport	= g_config.GetStrVal("MYSQL","port");
	const char* muser	= g_config.GetStrVal("MYSQL","user");
	const char* mpass	= g_config.GetStrVal("MYSQL","pass");
	const char* dbname	= g_config.GetStrVal("MYSQL","dbname");
	const char* tbname	= g_config.GetStrVal("MYSQL","tbname");

	if(!mserver || !mport || !muser || !mpass || !dbname || !tbname){
		log_error("MySQL config error");
		return false;
	}

	int type=atoi(src.c_str());
	if(type<=0){
		log_error("type <= 0");
		return false;
	}

	mysql_init(&g_conn);
	if((mysql_real_connect(&g_conn,mserver,muser,mpass,NULL,atoi(mport),NULL,0))==NULL){
		log_error("mysql_real_connect fail");
		return false;
	}


	map<string,pair<int8_t,int8_t>,greater<string> > dirtymap;

	sprintf(sql,"set names 'gbk'");
	mysql_query(&g_conn,sql);

	sprintf(sql,"select %s.%s.type,%s.%s.word,%s.%s.level from %s.%s where %s.%s.type=%d",dbname,tbname,dbname,tbname,dbname,tbname,dbname,tbname,dbname,tbname,type);
	if(mysql_query(&g_conn,sql)){
		log_error("mysql_query fail,sql = [%s]",sql);
		return false;
	}

	if(!(res_ptr=mysql_store_result(&g_conn))){
		log_error("mysql_store_result fail");
		return false;
	}

	while((sqlrow=mysql_fetch_row(res_ptr)))
	{
		int32_t type	= atoi(sqlrow[0]);
		string word(sqlrow[1]);
		int32_t level	= atoi(sqlrow[2]);
		dirtymap[word]	= make_pair<int8_t,int8_t>(type,level);
	}

	mysql_free_result(res_ptr);
	mysql_close(&g_conn);


	//map<string,int8_t>::iterator iter;
	map<string,pair<int8_t,int8_t> >::iterator iter;
	for(iter=dirtymap.begin();iter!=dirtymap.end();++iter)
		addDirty(src,iter->first,iter->second);

	return true;
}
